package com.example.javamaday.common;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import java.util.LinkedHashMap;
import java.util.Map;

public class LruCache<K, V> {

    private final LinkedHashMap<K, V> map;

    private int size;

    private int maxSize;

    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("LruCache <= 0");
        } else {
            this.maxSize = maxSize;
            /**
             * LinkedHashMap 构造函数，第三个参数accessOrder属性的时候说accessOrder设置为false时，
             * 按照插入顺序，设置为true时，按照访问顺序 -- 这个也是实现LruCache的核心配置。
             */
            this.map = new LinkedHashMap<>(0, 0.75F, true);
        }
    }


    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("LruCache <= 0");
        } else {
            this.maxSize = maxSize;
            trimToSize(maxSize);
        }
    }

    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("LruCache key == null");
        } else {
            V mapValue;
            synchronized (this) {
                mapValue = map.get(key);   // 调用get后，LinkedHashMap会根据访问顺序重新排序
                if (mapValue != null) {
                    return mapValue;
                }
            }
            V createValue = create(key);
            if (createValue == null) {
                return null;
            } else {
                synchronized (this) {
                    mapValue = map.put(key, createValue);
                }
                if (mapValue != null) {
                    map.put(key, mapValue);
                } else {
                    size += safeSizeOf(key, createValue);
                }
            }
            if (mapValue != null) {
                entryRemoved(false, key, createValue, mapValue);
                return mapValue;
            } else {
                trimToSize(maxSize);
                return createValue;
            }
        }
    }

    @Nullable
    public final V put(@NonNull K key, @NonNull V value) {
        V previous;
        synchronized (this) {
            this.size += this.safeSizeOf(key, value);
            previous = this.map.put(key, value);
            if (previous != null) {
                this.size -= this.safeSizeOf(key, previous);
            }
        }
        if (previous != null) {
            this.entryRemoved(false, key, previous, value);
        }
        this.trimToSize(this.maxSize);
        return previous;
    }

    @Nullable
    protected V create(@NonNull K key) {
        return null;
    }


    /**
     * trimToSize调整缓存大小，就是在一个死循环中，如果已缓存大小size大于设置的最大值maxSize，
     * 就把最近最少使用的元素删除掉，直到size小于等于maxSize就退出循环，trimToSize执行结束。
     *
     * @param maxSize 最大值
     */
    private void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || map.isEmpty() && size != 0) {
                    throw new IllegalStateException(this.getClass().getName() + ".sizeOf() is reporting inconsistent results!");
                }
                if (size < maxSize || map.isEmpty()) {
                    return;
                }
                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
            }
            this.entryRemoved(true, key, value, null);
        }
    }

    protected void entryRemoved(boolean evicted, @NonNull K key, @NonNull V oldValue, @Nullable Object newValue) {

    }

    private int safeSizeOf(K key, V value) {
        int result = this.sizeOf(key, value);
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        } else {
            return result;
        }
    }

    protected int sizeOf(@NonNull K key, @NonNull V value) {
        return 1;
    }

    public void string() {
        for (K k : map.keySet()) {
            System.out.println((String) k);
        }
    }

    public static void main(String[] args) {
        LruCache<String, String> lruCache = new LruCache<>(4);
        lruCache.put("1", "11");
        lruCache.put("2", "22");
        lruCache.put("3", "33");
        lruCache.get("1");
        lruCache.get("1");
        lruCache.put("4", "44");
        lruCache.get("1");
        lruCache.put("5", "55");
        lruCache.string();
        lruCache.trimToSize(3);
        lruCache.string();
    }


}
